package activity

import java.text.DateFormatSymbols
import org.springframework.context.i18n.LocaleContextHolder
import org.springframework.web.servlet.support.RequestContextUtils as RCU
import org.springframework.web.servlet.support.RequestDataValueProcessor

class BootstrapV3TagLib {
    static namespace = "m"
    
    def paginate = { attrs ->
        def writer = out
        if (attrs.total == null) {
            throwTagError("Tag [paginate] is missing required attribute [total]")
        }
        
        def messageSource               = grailsAttributes.messageSource
        def locale			= RCU.getLocale(request)
        
        def total			= attrs.int('total')		?: 0
        def action			= (attrs.action ? attrs.action : (params.action ? params.action : "list"))
        def offset			= params.int('offset')		?: 0
        def max				= params.int('max')
        def maxsteps		= (attrs.int('maxsteps')	?: 10)
        
        if (!offset)offset	= (attrs.int('offset')		?: 0)
        if (!max)	max		= (attrs.int('max')			?: 10)
        
        def linkParams = [:]
        if (attrs.params)	linkParams.putAll(attrs.params)
        linkParams.offset = offset - max
        linkParams.max = max
        if (params.sort)	linkParams.sort		= params.sort
        if (params.order)	linkParams.order	= params.order
        
        def linkTagAttrs = [action:action]
        if (attrs.controller)		linkTagAttrs.controller = attrs.controller
        if (attrs.id != null)		linkTagAttrs.id = attrs.id
        if (attrs.fragment != null)	linkTagAttrs.fragment = attrs.fragment
        linkTagAttrs.params = linkParams
        
        // determine paging variables
        def steps 		= maxsteps > 0
        int currentstep	= (offset / max) + 1
        int firststep	= 1
        int laststep	= Math.round(Math.ceil(total / max))
        
        // display previous link when not on firststep
        def disabledPrev = (currentstep > firststep) ? "" : "disabled"
        //		linkTagAttrs.class = 'prevLink'
        //		linkParams.offset = offset - max
        writer << "<ul class='pagination'>"
        writer << "<li class='prev ${disabledPrev}'>"
        if (disabledPrev) {
            //writer << '<a href="#">'+(attrs.prev ?: messageSource.getMessage('paginate.prev', null, messageSource.getMessage('default.paginate.prev', null, 'Previous', locale), locale)) + '</a>'
        } else {
            writer << link(linkTagAttrs.clone()) {
                (attrs.prev ?: messageSource.getMessage('paginate.prev', null, messageSource.getMessage('default.paginate.prev', null, 'Previous', locale), locale))
            }
        }
        writer << "</li>"
        
        // display steps when steps are enabled and laststep is not firststep
        if (steps && laststep > firststep) {
            linkTagAttrs.class = 'step'
            
            // determine begin and endstep paging variables
            int beginstep	= currentstep - Math.round(maxsteps / 2) + (maxsteps % 2)
            int endstep		= currentstep + Math.round(maxsteps / 2) - 1
            if (beginstep < firststep) {
                beginstep = firststep
                endstep = maxsteps
            }
            if (endstep > laststep) {
                beginstep = laststep - maxsteps + 1
                if (beginstep < firststep) {
                    beginstep = firststep
                }
                endstep = laststep
            }
            
            // display firststep link when beginstep is not firststep
            if (beginstep > firststep) {
                linkParams.offset = 0
                writer << "<li>"
                writer << link(linkTagAttrs.clone()) {firststep.toString()}
                writer << "</li>"
                writer << '<li class="disabled"><a href="#">…</a></li>'
            }
            
            // display paginate steps
            (beginstep..endstep).each { i ->
                if (currentstep == i) {
                    writer << "<li class='active'><a href='#'>"+i.toString()+"</a></li>"
                }
                else {
                    linkParams.offset = (i - 1) * max
                    writer << "<li>"
                    writer << link(linkTagAttrs.clone()) {i.toString()}
                    writer << "</li>"
                }
            }
            
            // display laststep link when endstep is not laststep
            if (endstep < laststep) {
                linkParams.offset = (laststep -1) * max
                writer << '<li class="disabled"><a href="#">…</a></li>'
                writer << "<li>"
                writer << link(linkTagAttrs.clone()) { laststep.toString() }
                writer << "</li>"
            }
        }
        
        // display next link when not on laststep
        def disabledNext = (currentstep < laststep) ? "" : "disabled"
        linkParams.offset = (currentstep)*max
        writer << "<li class='next ${disabledNext}'>"
        if (disabledNext) {
            //writer << '<a href="#">'+(attrs.prev ?: messageSource.getMessage('paginate.next', null, messageSource.getMessage('default.paginate.next', null, 'Next', locale), locale)) + '</a>'
        } else {
            writer << link(linkTagAttrs.clone()) {
                (attrs.next ? attrs.next : messageSource.getMessage('paginate.next', null, messageSource.getMessage('default.paginate.next', null, 'Next', locale), locale))
            }
        }
        writer << "</li>"
        writer << "</ul>"
    }
    
    def ckeditor = { attrs ->
        out << "<div style=\"display:inline-block\">\n"
        out << "${g.textArea(name:attrs.name,value:attrs.value)}\n"
        out << "${g.javascript(src:'ckeditor/ckeditor.js')}"
        out << "<script type=\"text/javascript\">CKEDITOR.replace('${attrs.name}', {width:${attrs.width?attrs.width:650}});</script>\n"
        out << "</div>\n"
    }
    
    def datePicker = { attrs ->
        def out = out // let x = x ?
        def xdefault = attrs['default']
        if (xdefault == null) {
            xdefault = new Date()
        }
        else if (xdefault.toString() != 'none') {
            if (xdefault instanceof String) {
                xdefault = DateFormat.getInstance().parse(xdefault)
            }
            else if (!(xdefault instanceof Date)) {
                throwTagError("Tag [datePicker] requires the default date to be a parseable String or a Date")
            }
        }
        else {
            xdefault = null
        }
        def years = attrs.years
        def relativeYears = attrs.relativeYears
        if (years != null && relativeYears != null) {
            throwTagError 'Tag [datePicker] does not allow both the years and relativeYears attributes to be used together.'
        }
        if (relativeYears != null) {
            if (!(relativeYears instanceof IntRange)) {
                // allow for a syntax like relativeYears="[-2..5]". The value there is a List containing an IntRage.
                if ((!(relativeYears instanceof List)) || (relativeYears.size() != 1) || (!(relativeYears[0] instanceof IntRange))) {
                    throwTagError 'The [datePicker] relativeYears attribute must be a range of int.'
                }
                relativeYears = relativeYears[0]
            }
        }
        def value = attrs.value
        if (value.toString() == 'none') {
            value = null
        }
        else if (!value) {
            value = xdefault
        }
        def name = attrs.name
        def id = attrs.id ?: name
        def noSelection = attrs.noSelection
        if (noSelection != null) {
            noSelection = noSelection.entrySet().iterator().next()
        }
        final PRECISION_RANKINGS = ["year": 0, "month": 10, "day": 20, "hour": 30, "minute": 40]
        def precision = (attrs.precision ? PRECISION_RANKINGS[attrs.precision] :
            (grailsApplication.config.grails.tags.datePicker.default.precision ?
                PRECISION_RANKINGS["${grailsApplication.config.grails.tags.datePicker.default.precision}"] :
                PRECISION_RANKINGS["minute"]))
        def day
        def month
        def year
        def hour
        def minute
        def dfs = new DateFormatSymbols(RCU.getLocale(request))
        def c = null
        if (value instanceof Calendar) {
            c = value
        }
        else if (value != null) {
            c = new GregorianCalendar()
            c.setTime(value)
        }
        if (c != null) {
            day = c.get(GregorianCalendar.DAY_OF_MONTH)
            month = c.get(GregorianCalendar.MONTH)
            year = c.get(GregorianCalendar.YEAR)
            hour = c.get(GregorianCalendar.HOUR_OF_DAY)
            minute = c.get(GregorianCalendar.MINUTE)
        }
        if (years == null) {
            def tempyear
            if (year == null) {
                // If no year, we need to get current year to setup a default range... ugly
                def tempc = new GregorianCalendar()
                tempc.setTime(new Date())
                tempyear = tempc.get(GregorianCalendar.YEAR)
            }
            else {
                tempyear = year
            }
            if (relativeYears) {
                if (relativeYears.reverse) {
                    years = (tempyear + relativeYears.toInt)..(tempyear + relativeYears.fromInt)
                } else {
                    years = (tempyear + relativeYears.fromInt)..(tempyear + relativeYears.toInt)
                }
            } else {
                years = (tempyear + 100)..(tempyear - 100)
            }
        }
        booleanToAttribute(attrs, 'disabled')
        booleanToAttribute(attrs, 'readonly')
        // Change this hidden to use requestDataValueProcessor
        def dateStructValue = processFormFieldValueIfNecessary("${name}","date.struct","hidden")
        out.println "<input type=\"hidden\" name=\"${name}\" value=\"${dateStructValue}\" />"
        // create year select
        if (precision >= PRECISION_RANKINGS["year"]) {
            out.println "<select name=\"${name}_year\" id=\"${id}_year\" aria-labelledby=\"${name}\""
            if (attrs.disabled) {
                out << ' disabled="disabled"'
            }
            if (attrs.readonly) {
                out << ' readonly="readonly"'
            }
            if (attrs.class) {
                out << ' class=' + attrs.class
            }
            out << '>'
            if (noSelection) {
                renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
                out.println()
            }
            for (i in years) {
                // Change this year option to use requestDataValueProcessor
                def yearIndex = processFormFieldValueIfNecessary("${name}_year","${i}","option")
                out.println "<option value=\"${yearIndex}\"${i == year ? ' selected="selected"' : ''}>${i}</option>"
            }
            out.println '</select>'
        }
        // create month select
        if (precision >= PRECISION_RANKINGS["month"]) {
            out.println "<select name=\"${name}_month\" id=\"${id}_month\" aria-labelledby=\"${name}\""
            if (attrs.disabled) {
                out << ' disabled="disabled"'
            }
            if (attrs.readonly) {
                out << ' readonly="readonly"'
            }
            if (attrs.class) {
                out << ' class=' + attrs.class
            }
            out << '>'
            if (noSelection) {
                renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
                out.println()
            }
            dfs.months.eachWithIndex {m, i ->
                if (m) {
                    def monthIndex = i + 1
                    monthIndex = processFormFieldValueIfNecessary("${name}_month","${monthIndex}","option")
                    out.println "<option value=\"${monthIndex}\"${i == month ? ' selected="selected"' : ''}>$m</option>"
                }
            }
            out.println '</select>'
        }        
        // create day select
        if (precision >= PRECISION_RANKINGS["day"]) {
            out.println "<select name=\"${name}_day\" id=\"${id}_day\" aria-labelledby=\"${name}\""
            if (attrs.disabled) {
                out << ' disabled="disabled"'
            }
            if (attrs.readonly) {
                out << ' readonly="readonly"'
            }
            if (attrs.class) {
                out << ' class=' + attrs.class
            }
            out << '>'
            if (noSelection) {
                renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
                out.println()
            }
            for (i in 1..31) {
                // Change this option to use requestDataValueProcessor
                def dayIndex = processFormFieldValueIfNecessary("${name}_day","${i}","option")
                out.println "<option value=\"${dayIndex}\"${i == day ? ' selected="selected"' : ''}>${i}</option>"
            }
            out.println '</select>'
        }
        // do hour select
        if (precision >= PRECISION_RANKINGS["hour"]) {
            out.println "<select name=\"${name}_hour\" id=\"${id}_hour\" aria-labelledby=\"${name}\""
            if (attrs.disabled) {
                out << ' disabled="disabled"'
            }
            if (attrs.readonly) {
                out << ' readonly="readonly"'
            }
            if (attrs.class) {
                out << ' class=' + attrs.class
            }
            out << '>'
            if (noSelection) {
                renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
                out.println()
            }
            for (i in 0..23) {
                def h = '' + i
                if (i < 10) h = '0' + h
                // This option add hour to requestDataValueProcessor
                h = processFormFieldValueIfNecessary("${name}_hour","${h}","option")
                out.println "<option value=\"${h}\"${i == hour ? ' selected="selected"' : ''}>$h</option>"
            }
            out.println '</select> :'
            // If we're rendering the hour, but not the minutes, then display the minutes as 00 in read-only format
            if (precision < PRECISION_RANKINGS["minute"]) {
                out.println '00'
            }
        }
        // do minute select
        if (precision >= PRECISION_RANKINGS["minute"]) {
            out.println "<select name=\"${name}_minute\" id=\"${id}_minute\" aria-labelledby=\"${name}\""
            if (attrs.disabled) {
                out << 'disabled="disabled"'
            }
            if (attrs.readonly) {
                out << 'readonly="readonly"'
            }
            if (attrs.class) {
                out << ' class=' + attrs.class
            }
            out << '>'
            if (noSelection) {
                renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
                out.println()
            }
            for (i in 0..59) {
                def m = '' + i
                if (i < 10) m = '0' + m
                m = processFormFieldValueIfNecessary("${name}_minute","${m}","option")
                out.println "<option value=\"${m}\"${i == minute ? ' selected="selected"' : ''}>$m</option>"
            }
            out.println '</select>'
        }
    }    

    //For datePicker
    private void booleanToAttribute(Map attrs, String attrName) {
        def attrValue = attrs.remove(attrName)
        if (attrValue instanceof CharSequence) {
            attrValue = attrValue.toString().trim()
        }
        // If the value is the same as the name or if it is a boolean value,
        // reintroduce the attribute to the map according to the w3c rules, so it is output later
        if ((attrValue instanceof Boolean && attrValue) ||
            (attrValue instanceof String && (((String)attrValue).equalsIgnoreCase("true") || ((String)attrValue).equalsIgnoreCase(attrName)))) {
            attrs.put(attrName, attrName)
        } else if (attrValue instanceof String && !((String)attrValue).equalsIgnoreCase("false")) {
            // If the value is not the string 'false', then we should just pass it on to
            // keep compatibility with existing code
            attrs.put(attrName, attrValue)
        }
    }    

    //For datePicker
    RequestDataValueProcessor requestDataValueProcessor
    private processFormFieldValueIfNecessary(name, value, type) {
        if (requestDataValueProcessor != null) {
            value = requestDataValueProcessor.processFormFieldValue(request, name, "${value}", type)
        }
        return value
    }    

    //For datePicker
    def renderNoSelectionOptionImpl(out, noSelectionKey, noSelectionValue, value) {
        // If a label for the '--Please choose--' first item is supplied, write it out
        out << "<option value=\"${(noSelectionKey == null ? '' : noSelectionKey)}\"${noSelectionKey == value ? ' selected="selected"' : ''}>${noSelectionValue.encodeAsHTML()}</option>"
    }    
}